/****************************************************************************
 *   Copyright (C) 2008 by 7552                                             *
 *   fiubataller2-08c1@gruposyahoo.com.ar                                   *
 *                                                                          *
 *  Licensed under the Apache License, Version 2.0 (the "License");         *
 *  you may not use this file except in compliance with the License.        *
 *  You may obtain a copy of the License at                                 *
 *                                                                          *
 *      http://www.apache.org/licenses/LICENSE-2.0                          *
 *                                                                          *
 *  Unless required by applicable law or agreed to in writing, software     *
 *  distributed under the License is distributed on an "AS IS" BASIS,       *
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.*
 *  See the License for the specific language governing permissions and     *
 *  limitations under the License.                                          *
 *                                                                          *
 ***************************************************************************/
#include "RecoveryManager.h"
#include <iostream>
using namespace std;
using namespace Taller2::TransRec;

//seteo inicial de insatncia
RecoveryManager * RecoveryManager::instance = NULL;

//**********************************************************
RecoveryManager::RecoveryManager()
{//*TODO arreglar esto, que no compila
//	ope=new IScheduler();
}

//**********************************************************
RecoveryManager* RecoveryManager::Instance(IScheduler* pScheduler)
{
	if ( !RecoveryManager::instance)
	{
	        RecoveryManager::instance = new RecoveryManager();
		instance->ope=pScheduler;
	}
    return RecoveryManager::instance;
}

//**********************************************************
void  RecoveryManager::Recover(const string& database)
{
	int tid;
	LogEntryType entryType;
	LogEntryType entryType_ant;
	map<int,streampos>::iterator iter;

	streampos pos;
	streampos pos_ant;

cout<<"inicia Recovery: "<<database<<endl;
	//iniciacion del proceso
	Init(database);

	//recorrido hacia atras
	pos= logCache->GetLastEntryType(entryType);
cout<<"pos:"<<pos<<endl;

	while(pos>=0 && entryType!=CHECKPOINT)
	{
		logCache->GetTransactionID(pos, tid);
cout<<"hacia atras - ENTRY:"<<entryType<<" TID:"<<tid<<" pos:"<<pos<<endl;
		if(entryType==INSERT || entryType==UPDATE || entryType==DELETE)
		{
			//busco en MapaCommited
			iter= MapaCommit.find(tid);
			if (iter==MapaCommit.end())
			{
				Rollback(pos,entryType); //si no hizo commit debo deshacerla
			}
		}

		if(entryType==COMMIT)
		{
			MapaCommit[tid]=pos; //se hizo un commit
		}

		pos_ant=pos;
		entryType_ant=entryType;
		pos=logCache->GetPrevEntryType(pos, entryType); //lee proximo tipo
	}

	//
	//TODO seguir rollback por lista de checkpoint
	//

	//recorro hacia adelante

	entryType=entryType_ant;
	//si es el primer registro
	if (pos<0)
		pos=0;

	while(pos>=0 && pos<finarch)
	{
cout<<"hacia adelante"<<endl;
		logCache->GetTransactionID(pos, tid);
		if(entryType==INSERT || entryType==UPDATE || entryType==DELETE)
		{
			iter= MapaCommit.find(tid);
			if (iter!=MapaCommit.end())
			{
				Redo(pos,entryType);//si hizo commit la rehago
			}
		}

		pos=pos_ant;
		pos_ant=logCache->GetNextEntryType(pos, entryType);
	}

	//finalizacion del proceso
	End();
}

//**********************************************************
void  RecoveryManager::Redo(streampos lsn, LogEntryType entryType)
{
	if( entryType==INSERT)
	{
		CInsertRecord IReg;
		logCache->LoadReg(lsn,IReg,true);
		ObjectId oid;
		ope->createObject(IReg.Object(),oid); 
	}
	if( entryType==DELETE)
	{
		CDeleteRecord DReg;
		logCache->LoadReg(lsn,DReg,false);
		ObjectId oid;
		oid.instanceId = DReg.ObjectID();
		ope->deleteObject(oid); 
	}

	if( entryType==UPDATE)
	{
		CUpdateRecord UReg;
		logCache->LoadReg(lsn,UReg,true);
		ope->updateObject(UReg.NewObject()); 
	}

}

//**********************************************************
void  RecoveryManager::Rollback(streampos lsn, LogEntryType entryType)
{
	
	if( entryType==INSERT)//si hizo inser hago delete
	{
		CInsertRecord IReg;
		logCache->LoadReg(lsn,IReg,false);
		ObjectId oid;
		oid.instanceId = IReg.ObjectID();
		ope->deleteObject(oid);
	}
	if( entryType==DELETE)//si hizo delete hago insert
	{
		CDeleteRecord DReg;
		logCache->LoadReg(lsn,DReg,true);
		ObjectId oid;
		ope->createObject(DReg.Object(),oid); 
	}

	if( entryType==UPDATE)//si actualizo, vuelvo al valor anterior
	{
		CUpdateRecord UReg;
		logCache->LoadReg(lsn,UReg,true);
		ope->updateObject(UReg.OldObject()); 
	}

}

//**********************************************************
void  RecoveryManager::Init(const string& database)
{

//cout<<s<<endl;
	logCache=new LogCache(database);

/*	fileLog.open("oodb.log",ios::in|ios::out|ios::binary);
	//calcula tama�o del archivo
	fileLog.seekg(0, ios_base::end);
	finarch=(int)fileLog.tellg();

*/
	//TODO
	//vaciar mapa
}

//**********************************************************
void  RecoveryManager::End()
{
	delete logCache;
}

//**********************************************************
/*int RecoveryManager::GetLastEntryType(LogEntryType &entry)
{
	entry = NONE;
	streampos iniPos = fileLog.tellg();

	int lastLSN;

	fileLog.seekg(-(streamoff)sizeof(int), ios_base::end);

	fileLog.read( reinterpret_cast<fstream::char_type*>(&lastLSN), sizeof(lastLSN));

	if (!fileLog.fail() && !fileLog.bad())
	{
		fileLog.seekg((streampos)lastLSN); // voy al comienzo del utlimo reg
		if (!fileLog.fail() && !fileLog.bad())
		{
			fileLog.read(reinterpret_cast<fstream::char_type*>(&entry), sizeof(LogEntryType)); // leo en entry type.
			if (!fileLog.fail() && !fileLog.bad())
			{
				fileLog.seekg(iniPos);
				return lastLSN ;
			}else
			{
				fileLog.seekg(iniPos);
				return -1;
			}
		}else
		{
			//cannot reach that position...
			fileLog.seekg(iniPos);
			return -1;
		};
	}else
	{
		fileLog.seekg(iniPos);
		return -1;
	};
};

//**********************************************************

int RecoveryManager::GetPrevEntryType(int pos, LogEntryType &entry)
{
	entry = NONE;
	streampos iniPos = fileLog.tellg();
	int lastLSN;

	fileLog.seekg((streampos)pos-(streampos)sizeof(int));
	if (!fileLog.fail() && !fileLog.bad())
	{
		fileLog.read(reinterpret_cast<fstream::char_type*>(&lastLSN), sizeof(lastLSN));
		if (!fileLog.fail() && !fileLog.bad())
		{
			//si no hay registro anterior
			if (lastLSN==-1)
			{
					fileLog.seekg(iniPos);
					return lastLSN;
			}
			fileLog.seekg((streampos)lastLSN);
			if (!fileLog.fail() && !fileLog.bad())
			{
				fileLog.read(reinterpret_cast<fstream::char_type*>(&entry), sizeof(LogEntryType)); // leo en entry type.
				if (!fileLog.fail() && !fileLog.bad())
				{
					fileLog.seekg(iniPos);
					return lastLSN;
				}else
				{
					fileLog.seekg(iniPos);
					return -1;
				};
			}else
			{
				fileLog.seekg(iniPos);
				return -1;
			}
		}else
		{
			fileLog.seekg(iniPos);
			return -1;
		}
	}else
	{
		//cannot reach that position...
		fileLog.seekg(iniPos);
		return -1;
	};
};

//**********************************************************

void RecoveryManager::GetTransactionID(int pos, int& transactionID)
{
	transactionID = -1;
	pos=pos+3*sizeof(int) + sizeof(LogEntryType);

	fileLog.seekg((streampos)pos);

	if (!fileLog.fail() && !fileLog.bad())
		fileLog.read(reinterpret_cast<fstream::char_type*>(&transactionID), sizeof(transactionID)); // leo en entry type
}

//**********************************************************
int RecoveryManager::GetNextEntryType(int pos, LogEntryType &entry)
{
	entry = NONE;
	streampos iniPos = fileLog.tellg();
	int lsn;

	fileLog.seekg((streampos)pos);
	if(fileLog.eof()) //si es el fin de archivo
	{
		return -1;
	}

	fileLog.read(reinterpret_cast<fstream::char_type*>(&entry), sizeof(LogEntryType)); // leo en entry type.
	fileLog.read(reinterpret_cast<fstream::char_type*>(&lsn), sizeof(int)); // leo el lsn

	fileLog.seekg(iniPos);
	return lsn;

};

//**********************************************************
/**
 * Estructura de una entrada de log:
 * [
 *	EntryType byte
 *	LSN Int32,
 *  lastLSNsameTransaction Int32,
 *  timestamp Int32 (segundos desde 1970-01-01 00:00:00),
 *  transactionID Int32,
 *  objectID Int64,
 *  oldObjectSize, newObjectSize, oldObject, newObject]
 *	lastLSN Int32,
 * La tira de bytes recibida en data representa la serializacion de los dos objetos
 */

//**********************************************************
